<?php
#
# UNLVSpatial: a dmBridge module for spatial content
#
# Copyright © 2011 Board of Regents of the Nevada System of Higher
# Education, on behalf of the University of Nevada, Las Vegas
#

/**
 * @author Alex Dolski <alex.dolski@unlv.edu>
 * @license http://www.opensource.org/licenses/mit-license.php
 */
class UNLVSpatialMySQLDataStore extends DMMySQLDataStore {

	private static $instance;

	/**
	 * Wrapped by getSetupSQLForMySQL() in the main module class (UNLVSpatial).
	 *
	 * @return array
	 */
	public static function getSetupSQL() {
		return array(
			"CREATE TABLE IF NOT EXISTS spatial_object (
				id int(10) unsigned NOT NULL auto_increment,
				alias varchar(30) NOT NULL,
				ptr int(10) unsigned NOT NULL,
				lat float default NULL,
				`long` float default NULL,
				lat_s float default NULL,
				lat_n float default NULL,
				long_w float default NULL,
				long_e float default NULL,
				timestamp timestamp default CURRENT_TIMESTAMP,
				PRIMARY KEY (id)
			);"
		);
	}

	/**
	 * Wrapped by getUpgradeSQLForMySQL() in the main module class (UNLVSpatial).
	 *
	 * @return array
	 */
	public static function getUpgradeSQL() {
		return array(
			// currently empty because there is no previous version from which
			// to upgrade.
		);
	}

	/**
	 * @return self object
	 * @since 0.3
	 */
	public static function getInstance() {
		if (!self::$instance instanceof self) {
			self::$instance = new self;
		}
		return self::$instance;
	}

	/**
	 * @param DMCollection $col
	 * @return int
	 */
	public function getNumObjectsInCollection($col) {
		$sql = UNLVSpatialGenericDataStore::getNumObjectsInCollectionSQL();
		$params = array(':alias' => $col->getAlias());
		$result = $this->read($sql, $params);
		return (int) $result[0]['count'];
	}

	/**
	 * Do not use this method to acquire a spatially-enabled object; use
	 * UNLVSpatialObjectFactory::spatializeObject() instead.
	 *
	 * @param DMCollection col
	 * @param int ptr
	 * @return UNLVSpatialObject, or null if the specified object has no
	 * spatial data.
	 */
	public function getObject(DMCollection $col, $ptr) {
		$select = "SELECT lat, `long`, lat_n, lat_s, long_w, long_e ";
		$from = "FROM spatial_object ";
		$where = "WHERE alias = :alias AND ptr = :ptr ";
		$limit = "LIMIT 1";
		$sql = $select . $from . $where . $limit . ";";

		$params = array(
			':alias' => $col->getAlias(),
			':ptr' => $ptr
		);

		$result = $this->read($sql, $params);

		if (count($result)) {
			if ($result[0]['lat_n']) { // it's a box
				$sp_obj = new UNLVSpatialRectObject();
				$sp_obj->setSpatialDataSource("local");
				$sp_obj->setSpatialNorthLat($result[0]['lat_n']);
				$sp_obj->setSpatialSouthLat($result[0]['lat_s']);
				$sp_obj->setSpatialWestLong($result[0]['long_w']);
				$sp_obj->setSpatialEastLong($result[0]['long_e']);
				return $sp_obj;
			} else if ($result[0]['lat']) { // it's a point
				$sp_obj = new UNLVSpatialPointObject();
				$sp_obj->setSpatialDataSource("local");
				$sp_obj->setSpatialLat($result[0]['lat']);
				$sp_obj->setSpatialLong($result[0]['long']);
				return $sp_obj;
			}
		}
		return null;
	}

	/**
	 * @param DMCollection col
	 */
	public function deleteAllObjectsInCollection(DMCollection $col) {
		$params = array(
			':alias' => $col->getAlias()
		);
		$sql = "DELETE FROM spatial_object WHERE alias = :alias";
		$this->write($sql, $params);
	}

	/**
	 * @param DMObject obj
	 * @return void
	 */
	public function deleteObject(DMObject $obj) {
		$sql = "DELETE FROM spatial_object
			WHERE alias = :alias AND ptr = :ptr";
		$params = array(
			':alias' => $obj->getCollection()->getAlias(),
			':ptr' => $obj->getPtr()
		);
		$this->write($sql, $params);
	}

	/**
	 * @param DMObject obj A DMObject that has been associated with a
	 * UNLVSpatialObject via DMModel::addAssociateModel().
	 */
	public function updateObject(DMObject $obj) {
		// decide whether to insert or to update
		$sql = "SELECT COUNT(id) AS count
			FROM spatial_object
			WHERE alias = :alias AND ptr = :ptr";
		$params = array(
			':alias' => $obj->getCollection()->getAlias(),
			':ptr' => $obj->getPtr()
		);
		$result = $this->read($sql, $params);

		if ($result[0]['count'] > 0) { // update
			$sql = UNLVSpatialGenericDataStore::getUpdateObjectSQL();
		} else { // insert
			$sql = UNLVSpatialGenericDataStore::getInsertObjectSQL();
		}

		$params = array(
			':alias' => $obj->getCollection()->getAlias(),
			':ptr' => $obj->getPtr(),
			':lat' => null,
			':long' => null,
			':lat_n' => null,
			':lat_s' => null,
			':long_w' => null,
			':long_e' => null
		);
		switch ($obj->getSpatialModelClassName()) {
		case "UNLVSpatialPointObject":
			$params[':lat'] = $obj->getSpatialLat();
			$params[':long'] = $obj->getSpatialLong();
			break;
		case "UNLVSpatialRectObject":
			$params[':lat_n'] = $obj->getSpatialNorthLat();
			$params[':lat_s'] = $obj->getSpatialSouthLat();
			$params[':long_w'] = $obj->getSpatialWestLong();
			$params[':long_e'] = $obj->getSpatialEastLong();
			break;
		}

		$this->write($sql, $params);
	}

	/**
	 * @param UNLVSpatialQuery query
	 * @return int
	 */
	public function getNumObjectsMatchingQuery(UNLVSpatialQuery $query) {
		$sql = UNLVSpatialGenericDataStore::getCountSQL($query);
		$result = $this->read($sql, array());
		return $result[0]['count'];
	}

	/**
	 * @param UNLVSpatialQuery query
	 * @return Array of DMObjects
	 */
	public function getObjectsMatchingQuery(UNLVSpatialQuery $query) {
		$sql = UNLVSpatialGenericDataStore::getSQL($query);
		$objects = array();
		foreach ($this->read($sql, array()) as $result) {
			$collection = DMCollectionFactory::getCollection($result['alias']);
			$obj = DMObjectFactory::getObject($collection, $result['ptr']);

			if ($result['lat_n']) { // it's a rect
				$sp_obj = new UNLVSpatialRectObject();
				$sp_obj->setSpatialNorthLat($result['lat_n']);
				$sp_obj->setSpatialSouthLat($result['lat_s']);
				$sp_obj->setSpatialWestLong($result['long_w']);
				$sp_obj->setSpatialEastLong($result['long_e']);
				$obj->addAssociatedModel($sp_obj);
			} else { // it's a point
				$sp_obj = new UNLVSpatialPointObject();
				$sp_obj->setSpatialLat($result['lat']);
				$sp_obj->setSpatialLong($result['long']);
				$obj->addAssociatedModel($sp_obj);
			}

			$objects[] = $obj;
		}
		return $objects;
	}

}
